################################################################################
class rap_states_jp(rap_states):
  """ Store set of Jp states. """
  def __init__(self,filename=None,**args):
    self.clear()
    if isinstance(filename,rap_states_lsj):
      self._take_lsj(filename)
    elif isinstance(filename,rap_states_jj):
      self._take_jj(filename)
    elif filename: self.loadfile(filename,**args)

# ------------------------------------------------------------------------------

  def _reprocess(self):
    """ Redefine ordinal numbers based on energy-order. """
    if len(self) == 0: return
    indices=zip(*sorted(zip(self.v_energy.values(),self.v_energy.keys())))[1]

    have={}
    tmp={}
    for idx in indices:
      (j2,par,ordi,en)=self.level(idx)

      # get and store seniority
      key=(j2,par)
      if key not in have: have[key]=0
      have[key]+=1
      self.v_ordinal[idx]=have[key]

# ------------------------------------------------------------------------------

  def _take_lsj(self,stlsj):
    """ Take in levels from rap_states_lsj instance. """
    if not isinstance(stlsj,rap_states_lsj):
      msg='expecting rap_states_lsj instance'
      raise ErrorRAP(msg)

    self.clear()
    self.set_ionization_threshold(stlsj.ionization_threshold())
    self.set_core_energy(stlsj.core_energy())
    self.set_nuclear_charge(stlsj.nuclear_charge())
    self.set_num_electrons(stlsj.num_electrons())
    toadd=[]
    for i in stlsj:
      (conf,term,alfa,j2,par,en)=stlsj.level(i)
      toadd.append((en,i,j2,par))
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def _take_jj(self,stjj):
    """ Take in levels from rap_states_jj instance. """
    if not isinstance(stjj,rap_states_jj):
      msg='expecting rap_states_jj instance'
      raise ErrorRAP(msg)

    self.clear()
    self.set_ionization_threshold(stjj.ionization_threshold())
    self.set_core_energy(stjj.core_energy())
    self.set_nuclear_charge(stjj.nuclear_charge())
    self.set_num_electrons(stjj.num_electrons())
    toadd=[]
    for i in stjj:
      (jconf,j2,alfa,par,en)=stjj.level(i)
      toadd.append((en,i,j2,par))
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def _parse_nist_line(self,line):
    """ 
    Function to extract level information (if any) from a NIST data file 
    (from webpage).  
    """
    if 'LIMIT' in line.upper():
      tmp=re.findall(r'(\d+\.\d+)',line)
      if not tmp: return False
      sym=line.split()[0].capitalize()
      zeff=None
      if sym in ATOMSYMBOLS: zeff=ATOMSYMBOLS.index(sym)
      ionth=float(tmp[-1])
      return (zeff,ionth)
    parts=line.split('|')
    if parts[0] == line: return False
    parity=None
    if parts[1].strip() != '':
      parity=0
      if '*' in parts[1]: parity=1
    tmp=re.findall(r'(\d+)/2',parts[2])
    if tmp == []:
      tmp=re.findall(r'(\d+)',parts[2])
      if tmp == []: return False
      jay=float(tmp[0])
    else:
      jay=0.5*float(tmp[0])
    tmp=re.findall(r'(\d+\.\d+)',parts[3])
    energy=None
    if tmp != []: energy=float(tmp[0].replace('+x',''))
    ne=None
    if energy == 0.0:
      conf=rap_config(parts[0])
      ne=conf.num_electrons()
    j2=int(2.0*jay)
    return (j2,parity,energy,ne)

# ------------------------------------------------------------------------------

  def clear(self):
    """ Erase all data. """
    rap_states.clear(self)
    self.v_cup='JP'
    self.v_j2={}
    self.v_ordinal={}

# ------------------------------------------------------------------------------

  def clear_levels(self):
    """ Just erase level data. """
    rap_states.clear_levels(self)
    self.v_cup='JP'
    self.v_j2={}
    self.v_ordinal={}

# ------------------------------------------------------------------------------

  def loadfile(self,filename,**args):
    """ Load in level information from level data file. """
    ft=rlf_filetype(filename)
    if ft == 'adf04':
      self.load_adf04(filename,**args)
    elif ft == 'olg':
      self.load_olg(filename,**args)
    elif ft == 'nist':
      self.load_nist(filename,**args)
    elif ft == 'chianti_elvlc':
      self.load_chianti(filename,**args)
    elif ft == 'x006':
      self.load_xstar(filename,**args)
    elif ft == 'fac_lev':
      self.load_fac(filename,**args)
    else:
      msg='do not know how to read file: %s'%filename
      raise ErrorRAP(msg)

# ------------------------------------------------------------------------------

  def reindex(self,remap=None):
    """ 
    Reassign level indices.  Indices are remapped according to dictionary,
    remap, old (key) -> new (value).  If remap is None, then reindex according
    to energy order.
    """
    imap=rap_states.reindex(self,remap)
    self.v_j2=dict([(imap[i],val) for (i,val) in self.v_j2.items()])
    self.v_parity=dict([(imap[i],val) for (i,val) in self.v_parity.items()])
    self.v_ordinal=dict([(imap[i],val) for (i,val) in self.v_ordinal.items()])

# ------------------------------------------------------------------------------

  def j2range(self,levlist=None,imap=False):
    """ Return tuple containing the minimum and maximum 2J values. """
    if levlist is None: levlist=self.indices()
    if imap is True: levlist=map(self.mapme,levlist)
    j2s=[self.v_j2[i] for i in levlist if i is not None]
    return (min(j2s),max(j2s))

# ------------------------------------------------------------------------------

  def j2(self,idx,imap=False):
    tidx=self._mapme(idx,imap)
    if tidx is None: return None
    return self.v_j2[tidx]

# ------------------------------------------------------------------------------

  def jrange(self,levlist=None,imap=False):
    """ Return tuple containing the minimum and maximum 2J values. """
    (j2min,j2max)=self.j2range(levlist,imap)
    return (0.5*float(j2min),0.5*float(j2max))

# ------------------------------------------------------------------------------

  def j(self,idx,imap=False):
    tidx=self._mapme(idx,imap)
    if tidx is None: return None
    return 0.5*float(self.v_j2[tidx])

# ------------------------------------------------------------------------------

  def ordinal(self,idx,imap=False):
    tidx=self._mapme(idx,imap)
    if tidx is None: return None
    return self.v_ordinal[tidx]

# ------------------------------------------------------------------------------

  def level(self,idx,imap=False):
    """ Return all information for single level (j2,par,ordi,E) """
    tidx=self._mapme(idx,imap)
    if tidx is None: return (None,None,None,None)
    out=(self.v_j2[tidx],self.v_parity[tidx],self.v_ordinal[tidx],\
         self.v_energy[tidx])
    return out

# ------------------------------------------------------------------------------

  def label(self,idx,imap=False,fraction=True,spacer=None):
    """ 
    Return level label.

    When fraction is True, return fractional form of J, else decimal form.
    When spacer is not None, use it as an alternative to a space in the label.
    """
    tidx=self._mapme(idx,imap)
    if tidx is None: return None
    j2=self.j2(idx,imap=imap)
    pval=self.parity(idx,imap=imap)
    ordi=self.ordinal(idx,imap=imap)
    oval=rlf_ordinal(ordi)
    if fraction:
      if j2%2 == 0:
        jstr=str(j2/2)
      else:
        jstr=str(j2)+'/2'
    else:
      jstr='%.1f'%(0.5*float(j2))
    pstr='e'
    if pval == 1: pstr='o'
    out='%s J=%s%s'%(oval,jstr,pstr)
    if spacer is not None: out=out.replace(' ',spacer)
    return out

# ------------------------------------------------------------------------------

  def weight(self,idx,imap=False):
    """ Return weight of level. """
    tidx=self._mapme(idx,imap)
    if tidx is None: return None
    return self.j2(idx,imap=imap)+1

# ------------------------------------------------------------------------------

  def add_level(self,j2,par,energy,idx=False):
    """ Add new level to list """
    if idx is not False and idx in self:
      msg='index already exists: %s'%idx
      raise ErrorRAP(msg)
    tidx=idx
    if tidx is False: 
      tidx=1
      if len(self) > 0: tidx=max(self.indices())+1
    if par not in [0,1]:
      msg='illegal value of parity'
      raise ErrorRAP(msg)
    # check if 2J consistent with other levels (integer v. half-integer)
    if len(self) > 0:
      ichk=self.indices()[0]
      chk1=self.j2(ichk)%2
      chk2=j2%2
      if chk1 != chk2:
        if chk1 == 0:
          msg='new level must have an integral J-value'
        else:
          msg='new level must have a half-integral J-value'
        raise ErrorRAP(msg)
    self.v_j2[tidx]=j2
    self.v_parity[tidx]=par
    self.v_energy[tidx]=energy
    self.v_ordinal[tidx]=-1
    self._reprocess()

# ------------------------------------------------------------------------------

  def add_levels(self,toadd,idxstart=None):
    """ 
    Add set of levels to structure. 

    The input list, toadd, contains tuples with: energy (Ry), index, 2J-value,
    and parity.  If the indexing is to be automated, then set index in the
    tuples to -1.
    """
    if len(toadd) == 0: return
    toadd.sort()
    idxcur=1
    if len(self) > 0: idxcur=max(self.indices())+1
    if idxstart is not None:
      if idxstart < idxcur: 
        msg='invalid starting index: %s'%idxstart
        raise ErrorRAP(msg)
      idxcur=idxstart
    tmp={}
    for (en,idx,j2,par) in toadd:
      tidx=idx
      if tidx in self or tidx in tmp: 
        msg='duplicate index: %s'%tidx
        raise ErrorRAP(msg)
      if tidx < 0:
        tidx=idxcur
        idxcur+=1
      tmp[tidx]=(en,j2,par)
    for idx,(en,j2,par) in tmp.iteritems():
      self.v_j2[idx]=j2
      self.v_parity[idx]=par
      self.v_energy[idx]=en
      self.v_ordinal[idx]=-1
    self._reprocess()

# ------------------------------------------------------------------------------

  def set_num_electrons(self,nelec):
    """ Set number of electrons. """
    self.v_nelec=int(nelec)

# ------------------------------------------------------------------------------

  def set_lmap(self,other,simple=False):
    """ Map to other rap_states_jp instance. """
    if not isinstance(other,rap_states_jp):
      msg='argument must be of type rap_states_jp'
      raise ErrorRAP(msg)

    if simple:
      tmap={}
      for i in other:
        k=None
        for j in self:
          if self[j] == other[i]:
            k=j
            break
        tmap[i]=k
      self.v_lmap=tmap
      self.v_lmap_r=dict([(v,k) for k,v in self.v_lmap.items()])
      return

    # group level indices by (2J,par)
    fromlist={}
    for i in self:
      key=(self.j2(i),self.parity(i))
      if key not in fromlist: fromlist[key]=[]
      fromlist[key].append(i)
    tolist={}
    for j in other:
      key=(other.j2(j),other.parity(j))
      if key not in tolist: tolist[key]=[]
      tolist[key].append(j)

    self.v_lmap=self._map_by_energy(other,fromlist,tolist)
    for j in other:
      if j not in self.v_lmap: self.v_lmap[j]=None
    self.v_lmap_r=dict([(v,k) for k,v in self.v_lmap.items() if v is not None])
    for i in self:
      if i not in self.v_lmap_r: self.v_lmap_r[i]=None

# ------------------------------------------------------------------------------

  def j2filter(self,j2list,subset=None,imap=False):
    """ Filter list of states based on 2J values in j2list. """
    if not isinstance(j2list,list) and not isinstance(j2list,tuple): 
      j2list=[j2list]
    tj2list=map(int,j2list)

    idx=self.indices()
    if subset is not None:
      idx=subset[:]
      if imap is True: idx=self.lmap(idx)

    out=[]
    for i in idx:
      if self.j2(i) in tj2list: out.append(i)
    if imap is True: return self.lmap_reverse(out)
    return out

# ------------------------------------------------------------------------------

  def jfilter(self,jlist,subset=None,imap=False):
    """ Filter list of states based on J values in j2list. """
    if not isinstance(jlist,list) and not isinstance(jlist,tuple): jlist=[jlist]
    tjlist=map(float,jlist)

    idx=self.indices()
    if subset is not None:
      idx=subset[:]
      if imap is True: idx=self.lmap(idx)

    out=[]
    for i in idx:
      if self.j(i) in tjlist: out.append(i)
    if imap is True: return self.lmap_reverse(out)
    return out

# ------------------------------------------------------------------------------

  def symfilter(self,j2,p=None,subset=None,imap=False):
    """ Filter list of states based on symmetry. """
    if p is None:
      try:
        (jj2,pp)=j2
      except:
        msg='do not understand symmetry'
        raise ErrorRAP(msg)
    else:
      jj2=j2
      pp=p

    idx=self.indices()
    if subset is not None:
      idx=subset[:]
      if imap is True: idx=self.lmap(idx)

    out=[]
    for i in idx:
      tj2=self.j2(i)
      tp=self.parity(i)
      if tj2 != jj2 or tp != pp: continue
      out.append(i)
    if imap is True: return self.lmap_reverse(out)
    return out

# ------------------------------------------------------------------------------

  def load_olg(self,filename):
    """ Load in level information from an AUTOSTRUCTURE olg file """
    fin=open(filename,'r')

    # get nuclear charge
    while 1:
      line=fin.readline()
      if line == '':
        msg='file ended before retrieval of nuclear charge'
        raise ErrorRAP(msg)
      if 'ATOMIC NUMBER' in line: break
    tmp=re.findall(r'ATOMIC NUMBER\s*(\d+)',line)
    zeff=int(tmp[0])
    tmp=re.findall(r'NUMBER OF ELECTRONS\s*(\d+)',line)
    ne=int(tmp[0])

    # get level information
    while 1:
      line=fin.readline()
      if line == '': 
        msg='no level data found'
        raise ErrorRAP(msg)
      if '(EK-E1)/RY' in line: break
    tmp=re.findall(r'(-?\d+\.\d+)\s*$',line)
    core_energy=None
    if tmp: core_energy=float(tmp[0])
    toadd=[]
    while 1:
      line=fin.readline()
      tmp=line.split()
      if tmp == []: break
      idx=int(tmp[0])
      j2=int(tmp[6])
      p=0
      if int(tmp[4]) < 0: p=1
      energy=float(tmp[8])
      toadd.append((energy,idx,j2,p))
    fin.close()
    self.set_nuclear_charge(zeff)
    self.set_num_electrons(ne)
    self.set_core_energy(core_energy)
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def load_adf04(self,filename):
    """ Load in level information from an ADAS adf04 file """
    fin=open(filename,'r')

    # basic info
    line=fin.readline()
    tmp=re.findall(r'([\d.]+)',line)
    z=int(tmp[1])
    q=int(tmp[2])
    ip=float(tmp[3])
    ne=z-q+1

    # read in level data
    isic=True
    toadd=[]
    while 1:
      line=fin.readline()
      if '-1' in line: break
      tmp=re.findall(r'(\d+)(.+)\((.+)\)(.+)\((.+)\)\s+(\d+[\.\d]*)',line)[0]
      idx=int(tmp[0])
      conf=rap_config(tmp[1],ne=ne)
      p=conf.parity()
      g=int(tmp[2])
      l=int(tmp[3])
      j2=int(2.*float(tmp[4]))
      e=float(tmp[5])/INVERSECM
      toadd.append((e,idx,j2,p))

      wgt=j2+1
      if wgt not in  range(abs(2*l-g+1)+1,2*l+g+1,2): isic=False
    fin.close()

    # check for correct coupling scheme
    if not isic:
      msg='adf04 file must be LSJ in order to load into rap_states_jp'
      raise ErrorRAP(msg)

    self.set_nuclear_charge(z)
    self.set_num_electrons(ne)
    self.set_ionization_threshold(ip)
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def load_nist(self,filename):
    """ Load in NIST data file (text file taken from website). """
    zeff=None
    nelec=None
    toadd=[]
    ionth=[]
    parity=None
    fin=open(filename,'r')
    for line in fin:
      tmp=self._parse_nist_line(line)
      if tmp:
        if len(tmp) == 2:
          if tmp[0] is not None: zeff=tmp[0]
          ionth.append(tmp[1])
        else:
          (j2,par,en,ne)=tmp
          if par is not None: parity=par
          if ne is not None: nelec=ne
          toadd.append((en,-1,j2,parity))
    fin.close()

    # remove states with no energy
    for i in range(len(toadd)-1,-1,-1):
      if toadd[i][0] is None: toadd.pop(i)

    if len(ionth) > 0: ip=min(ionth)
    self.set_nuclear_charge(zeff)
    if nelec is not None: self.set_num_electrons(nelec)
    self.set_ionization_threshold(ip)
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def load_chianti(self,filename,priority="experimental",only=False):
    """ 
    Read energy levels from CHIANTI file.  CHIANTI energy level files contain
    both theoretical and experimental energies.  The priority option allows
    you to select which energy takes precedence (if available).  The only flag
    will restrict this function to storing only levels where the priority
    energy is present.
    """
    id_idx=0         # level index
    id_k=1           # configuration index
    id_conf=2        # configuration string
    id_g=3           # 2S+1
    id_lval=4        # L (integer)
    id_l=5           # L (string)
    id_j=6           # J
    id_wgt=7         # weight (2J+1)
    id_obcm=8        # observed energy (1/cm)
    id_obry=9        # observed energy (Ry); bad conversion, use 1/cm value
    id_thcm=10       # theoretical energy (1/cm)
    id_thry=11       # theoretical energy (Ry); bad conversion, use 1/cm value

    # attempt to get nuclear charge from filename
    tmp=re.findall(r'(\w+)_\d+\.elvlc',filename)
    if tmp:  
      zeff=ATOMSYMBOLS.index(tmp[0].capitalize())
      self.set_nuclear_charge(zeff)

    ne=None
    toadd=[]
    fin=open(filename,'r')
    for line in fin:
      tmp=line.split()
      if tmp[0] == '-1': break
      if len(tmp) != 12:
        raise RuntimeError('cannot undertand CHIANTI energy level file')
      idx=int(tmp[id_idx])
      conf=rap_config(tmp[id_conf],ne=ne)
      parity=conf.parity()
      g=int(tmp[id_g])
      l=int(tmp[id_lval])
      term=rlf_termencode(g,l)
      j2=int(tmp[id_wgt])-1
      ob=float(tmp[id_obcm])/INVERSECM
      th=float(tmp[id_thcm])/INVERSECM
      if priority[0] == 't':
        en=th
        if en == 0.0 and idx > 1: 
          if only is True: continue
          en=ob
      else:
        en=ob
        if en == 0.0 and idx > 1: 
          if only is True: continue
          en=th
      if ne is None: ne=conf.num_electrons()
      toadd.append((en,idx,j2,parity))
    fin.close()
    self.set_num_electrons(ne)
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def load_xstar(self,filename):
    """ 
    Load level information from xstar type 006 file.
    """
    # column id's
    id_energy=7
    id_j2=8
    id_ip=10
    id_g=12
    id_l=13
    id_z=14
    id_ion=16
    id_id=15
    id_conf=17

    # read level list from file
    ion=None
    ne=None
    ldata=[]
    have=[]
    xid0=None
    fin=open(filename,'r')
    for line in fin:
      data=line.split()

      confstr=data[id_conf-1]
      xid=int(data[id_id-1])
      ion=int(data[id_ion-1])
      g=abs(int(data[id_g-1]))
      l=int(data[id_l-1])
      j2=int(float(data[id_j2-1]))-1
      w=g*(2*l+1)
      if g == 1 or l == 0:
        cup='XX'
      elif w < j2+1:
        cup='CA'
        if 'CA' not in have: have.append('CA')
      elif w == j2+1:
        cup='LS'
        if 'LS' not in have: have.append('LS')
      else:
        j2min=abs(g-1-2*l)
        j2max=g-1+2*l
        okay=False
        for j2t in range(j2min,j2max+1,2):
          if j2t == j2: okay=True
        if okay:
          cup='IC'
          if 'IC' not in have: have.append('IC')
        else:
          cup='??'
      e=float(data[id_energy-1])
      if e == 0.0:
        ne=rap_config(confstr).num_electrons()
        zeff=int(data[id_z-1])
        ion_thresh=float(data[id_ip-1])
      chk=confstr.lower()
      if 'su' in chk or 'sp' in chk: continue 
      if 'co' in chk or 'cn' in chk: continue
      ldata.append([e/RYDBERG,cup,confstr,g,l,j2,xid])
    fin.close()
    if ne is None: raise RuntimeError('cannot find ground state')

    # id's for ldata
    id_e=0
    id_cup=1
    id_conf=2
    id_g=3
    id_l=4
    id_j2=5
    id_xid=6

    # get number of electrons from ground state and standardize configurations
    for i in range(len(ldata)):
      ldata[i][id_conf]=rap_config(ldata[i][id_conf],ne=ne)

    # get total weight of each configuration
    weight={}
    wtot={}
    cups={}
    for i in range(len(ldata)):
      conf=str(ldata[i][id_conf])
      if conf not in wtot: wtot[conf]=ldata[i][id_conf].weight()
      if conf not in cups: cups[conf]=[]
      if ldata[i][id_cup] not in cups[conf]: cups[conf].append(ldata[i][id_cup])
      if ldata[i][id_cup] in ['XX','LS']:
        if conf not in weight:
          weight[conf]=ldata[i][id_g]*(2*ldata[i][id_l]+1)
        else:
          weight[conf]+=ldata[i][id_g]*(2*ldata[i][id_l]+1)
      else:
        if conf not in weight:
          weight[conf]=(ldata[i][id_j2]+1)
        else:
          weight[conf]+=(ldata[i][id_j2]+1)

    # check if configurations have a common coupling scheme
    for c in cups.keys():
      tmp=cups[c][:]
      if 'XX' in tmp: tmp.remove('XX')
      if len(tmp) > 1:
        raise RuntimeError('mixed coupling scheme not supported')
      if tmp == [] and len(have) == 1: tmp=have[:]
      cups[c]=tmp[:]

    # check that 'IC' configurations have all possible levels
    for c in weight.keys():
      if 'IC' not in cups[c]: continue
      elif weight[c] > wtot[c]:
        raise RuntimeError('configuration, '+str(c)+', has too many levels')

    # add levels having IC coupling
    toadd=[]
    for lev in ldata:
      idx=lev[id_xid]
      conf=lev[id_conf]
      sc=str(conf)
      if 'IC' not in cups[sc] and cups[sc] != ['XX']: continue
      j2=lev[id_j2]
      parity=conf.parity()
      energy=lev[id_e]
      toadd.append((energy,idx,j2,parity))
    if len(toadd) == 0:
      msg='no LSJ data found in file, %s'%filename
      raise ErrorRAP(msg)
    self.set_nuclear_charge(zeff)
    self.set_num_electrons(ne)
    self.set_ionization_threshold(ion_thresh/RYDBERG)
    self.add_levels(toadd)

# ------------------------------------------------------------------------------

  def load_fac(self,filename,nelec=None):
    """ 
    Load in level information from a FAC energy level file. 

    The optional argument, nelec, will tell the function to look for levels with
    the given number of electrons.  Otherwise, the first level set is taken.
    """
    fin=open(filename,'r')

    # basic info
    z=None
    ne=None
    core=None
    nlev=None
    while 1:
      line=fin.readline()
      if line.strip() == '': break
      if ' Z = ' in line:
        z=int(line.split()[-1])
      elif 'E0' in line:
        e0=float(line.split()[-1])

    # loop over blocks
    done=False
    toadd=[]
    while 1:
      if done: break

      # level set info
      while 1:
        line=fin.readline()
        if line == '':
          done=True
          break
        if 'VNL' in line: break
        if 'NELE' in line:
          ne=int(line.split()[-1])
        elif 'NLEV' in line:
          nlev=int(line.split()[-1])
      if nelec is not None and ne != nelec: continue

      # read in level data
      toadd=[]
      while 1:
        line=fin.readline()
        if line.strip() == '': break
        tmp=line.split()
        idx=int(tmp[0])
        en=float(tmp[2])/RYDBERG
        j2=int(tmp[5])

        tmp=re.findall(r'([\w+-]+\(\d+\)\d+)',line)
        tconf=' '.join(tmp)
        conf=rap_jconfig(tconf)
        par=conf.parity()

        toadd.append((en,idx,j2,par))
      if len(toadd) > 0: break
    fin.close()

    if len(toadd) == 0:
      if nelec is None:
        msg="level data found found"
      else:
        pl=''
        if nelec > 1: pl='s'
        msg="level data with %i electron%s not found"%(nelec,pl)
      raise ErrorRAP(msg) 

    self.set_nuclear_charge(z)
    if ne is not None: self.set_num_electrons(ne)
    self.set_core_energy(core)
    self.add_levels(toadd)

